#include <Material.hpp>
#include <Sphere.hpp>
#include <glm/gtc/random.hpp>

Sphere::~Sphere() = default;

bool Sphere::Hit(const Ray& ray, float t_min, float t_max,
                 HitRecord& rec) const {
  auto oc = ray.Origin() - mCenter;
  auto a = glm::dot(ray.Direction(), ray.Direction());
  auto half_b = glm::dot(oc, ray.Direction());
  auto c = glm::dot(oc, oc) - mRadius * mRadius;
  auto discriminant = half_b * half_b - a * c;

  if (discriminant < 0.f) {
    return false;
  }

  // Find the nearest root that lies in the acceptable range.
  float sqrt_d = glm::sqrt(discriminant);
  auto root = (-half_b - sqrt_d) / a;
  if (root < t_min || root > t_max) {
    root = (-half_b + sqrt_d) / a;
    if (root < t_min || root > t_max) {
      return false;
    }
  }

  rec.t = root;
  rec.p = ray.At(root);
  glm::vec3 outward_normal = (rec.p - mCenter) / mRadius;
  rec.SetFaceNormal(ray, outward_normal);
  rec.material = mMaterial.get();

  return true;
}

glm::vec3 Sphere::RandomInHemisphere(const glm::vec3& normal) {
  glm::vec3 in_unit_sphere = glm::sphericalRand(1.f);
  if (glm::dot(in_unit_sphere, normal) > 0.f) {
    // In the same hemisphere
    return in_unit_sphere;
  } else {
    return -in_unit_sphere;
  }
}
